MOOC算法学习笔记 - 动态规划(1)

中国大学mooc视频地址 程序设计与算法(二)算法基础

直接看一道例题

例题一 数字三角形

(一)采用递归方法

思路:此题是标准的递归思维
首先想想第一步怎么走, 首先确定第一个是D[i][j],那么下一步是D[i+1][j]或 D[i+1][j+1]
如此便形成相似的循环,可以用递归解决 那么边界条件是什么呢?当递归到最 后一行的时候就可以跳出 即if(i==n)为边界条件。
【将原问题拆为若干子问题,在这里原问题(D[i][j]到最后的最大和)的子问题就是D[i][j]的下一个D[i+1][j]或D[i+1][j+1]到最后的最大和】

·······总结下来就是
一个位置到最后的最大和 等于 这个位置的[i+1][j]和[i+1][j+1]中到最后的最大和的较大值 加上这个位置本身

(递归代码如下:

#include<iostream>
#include<algorithm>
#define MAX 101
using namespace std;
int D[MAX][MAX];
int n;
int MaxSum(int i, int j){
    if(i==n)
        return D[i][j]
    else{
        int x = MaxSum(i+1,j);
        int y = MaxSum(i+1,j+1);
        return max(x,y)+D[i][j];
}
int main()
{
    int i,j;
    cin >> n;
    for(i = 1; i <= n; i++)
        for(j = 1; j <= i; j++)
            cin >> D[i][j];
    cout << MaxSum(1,1) <<endl;
    return 0;
}

(二)采用记忆递归型动归

解决这种重复计算的方法即记下之前算过的从而避免重复:
数字三角形的记忆递归型动归程序:

动归代码如下:

#include<iostream>
#include<algorithm>
#define MAX 101
using namespace std;
int D[MAX][MAX];
int maxSum[MAX][MAX];        //存放算过的值
int n;
int MaxSum(int i, int j){
    if(maxSum[i][j] != -1)        //判断是否算过了
        return maxSum[i][j];
    if(i==n)
        maxSum[i][j] = D[i][j];
    else{
        int x = MaxSum(i+1,j);
        int y = MaxSum(i+1,j+1);
        maxSum[i][j] = max(x,y) + D[i][j];
    }
    return maxSum[i][j];        //整体return了 这点仔细想想
int main()
{
    int i,j;
    cin >> n;
    for(i = 1; i <= n; i++)
        for(j = 1; j <= i; j++){
            cin >> D[i][j];
            maxSum[i][j] = -1;
        }
    cout << MaxSum(1,1) <<endl;
    return 0;
}

(三)将递归进阶成递推

重新分析,可以从最后一行开始,一行行往上递推

#include<iostream>
#define MAX 101
using namespace std;
int main()
{
    int D[MAX][MAX];
    int maxSum[MAX][MAX];
    int n,i,j;
    cin >> n;
    for(i = 1; i <= n; i++)
        for(j = 1;j <=i; j++)
            cin >> D[i][j];
    for(i = 1; i <=n; i++)            //对边界条件进行初始化
        maxSum[n][i] = D[n][i];        //最后一行的结果就是它本身
    for(i = n-1; i>=1; i--)        //从倒是第二行开始往上递推,
        for(j = 1; j <=i; j++)
            maxSum[i][j] = max(maxSum[i+1][j],maxSum[i+1][j+1])+D[i][j];
    cout << maxSum[1][1] <<endl;
    return 0;
}

(四)递推的空间优化 —滚动数组(存疑)

在三的递推中,发现其实并不需要一个maxSum[i][j]来记录结果,可以用一个maxSum[i]一维数组来记录,新纪录的可以将旧的结果覆盖,因为不要再用它了,然后再想想 发现其实可以用一个*maxSum 也可以实现

#include<iostream>
#define MAX 101
using namespace std;
int main()
{
    int D[MAX][MAX];
    int *maxSum;
    int n,i,j;
    cin >> n;
    for(i = 1; i <= n; i++)
        for(j = 1;j <=i; j++)
            cin >> D[i][j];
    maxSum = D[n];
    for(i = n-1; i>=1; i--)
        for(j = 1; j <=i; j++)
            maxSum[j] = max(maxSum[j],maxSum[j+1])+D[i][j];
    cout << maxSum[1] <<endl;
    return 0;
}

(五)总结:从递归到递推/动规

  • **··递归到递推的一般转化方法:
    1.递归函数有n个参数,就定义一个n维的数组 
    2.数组的下标是递归函数参数的取值范围    
    3.数组元素的值是递归函数的返回值    
    4.从边界值出发,逐步填充数组,相当于计算递归函数值的逆过程**
    
  • **··动规解题的一般思路:
    1.将原问题拆分成若干小问题(类似于递归的思路),但与递归不同的是要将每次计算的结果存下来,避免重复计算。 【可参照上面数字三角形的例子】
    2.确定状态
    一个状态就是一个子问题,上述例子有多少数字就有多少状态
    3.确定一些初始状态(边界状态)的值
    4.确定状态转移方程
        如何从一个或多个已知值的状态 求出另一个状态的值
        可以用递推式表示,即状态转移方程。
        如数字三角形中的状态转移方程**
    

··能用动规解决的问题的特点:
1.问题具有最优子结构性质。
指问题的最优解所包含的子问题的解也是最优的
2.无后效性
当前的若干个状态值一旦确定,则此后过程的演变就只和这若干个状态值有关,和之前是采取哪种手段或哪条路径演变到当前的这若干个状态没关